What
can we do to make our endpoints as service-oriented as possible? We can
retain our focus on reusability, abstraction, interoperability, and
loose coupling in order to accomplish this. One way to do this is
embrace data mapping and not be hamstrung by the idea that the bus
should only accept a single canonical schema. If we don't force service
callers to all implement a specific data format, we can instead grow
our set of callers organically and bring on new clients with ease.
Building reusable receive ports
One
key way to make our services as interoperable as possible is to offer a
range of inbound transmission channels. While it would be ideal if all
our service clients were running the latest versions of the .NET
framework, in reality, we are frequently interacting with either dated
or cross-platform service consumers. Also, while we may have all
service consumers on the same platform, they may all define a
particular data entity in slightly different ways. Let's look at how
we'd accommodate each of these scenarios.
In
this first scenario, consider an environment where our service callers
each represent different vendors, and thus each have different service
capabilities. One vendor has existing VPN access and is quite modern in
their development framework and wants to use the TCP protocol. Another
vendor is forced to go through a public Internet connection and its
service client only supports SOAP Basic Profile 1.1. As we've already
discussed so far, neither of these considerations need to factor into
the development of our BizTalk components. Let's assume we have a new
schema that holds registration details for physicians wishing to
administer a clinical trial for a new medicine. The corresponding
orchestration takes in this physician registration message and
processes it accordingly. Nothing in this orchestration has to
influence which type of inbound receive channel we wish to use.
When
this orchestration is deployed, we get to bond the logical receive port
to the physical one. In this case, we have a single receive port, but
have the option of multiple receive locations. Each receive location
corresponds to a different input mechanism whether it be a classic FILE
adapter, or WCF channels such as HTTP, or MSMQ. Given how easy it is to
add new receiving endpoints, it is good form to offer multiple input
channels that accommodate the widest range of consumers.
Now
what if the physician registration message that my orchestration
accepts was different than the format that each vendor had been
transmitting for years prior? If I want the lowest service consumer
impact, I'd want to let them send whatever format they use, and simply
normalize it to the canonical format required by my orchestration. I
have two real choices here:
Create
facade endpoints for each (new) service client so that each client has
a strongly typed service to call. All of these facade endpoints can
still be part of the same physical receive port and maps are applied to
get the data into a common format.
Create
a single, generic endpoint that on boards all registration messages and
applies an assortment of maps to reshape the data into our common
format.
Choosing
the former option requires more maintenance for all parties, but
follows better form. The latter option is quite flexible, but raises
the risk of improperly formatted data reaching the bus.
So,
when it comes to reusing receive ports, consider not only applying
multiple receive locations but also adding multiple maps to accommodate
a single receive location. A physical receive port offers a valuable
level of abstraction to the message bus and any linked orchestrations
so that we can control significant behavior at the outermost perimeter
of the messaging infrastructure. We can use the same receive port over
and over for a related process by simply standing up new input channels
and applying new maps to the canonical format.
Constructing a contract-first endpoint
Earlier
we discussed the fact that BizTalk Server receive locations are
inherently "type-less". That is, they dictate no specific contract.
While the BizTalk WCF Service Publishing Wizard
does a fairly efficient job building metadata endpoints, there are
simply cases where you want more control over the contract being
exposed to service clients. Fortunately for us, the BizTalk WCF
adapters do a nice job of allowing externally defined WSDL definitions
to front the BizTalk endpoints.
Earlier
in the book, I alluded to the fact that you can technically host an
HTTP-based WCF endpoint within the BizTalk in-process host. That is,
you don't have to use IIS. For the example we walk through here, I'll
use that particular hosting pattern. To legitimately do contract-first
service development, you build a schema and WSDL file first, and
massage the resulting application to accommodate that contract
definition. We can start out with a simple SiteRegistration schema which provides all the details we need to associate a site with a particular clinical trial.
The
next step is to create a basic WSDL, which uses this schema and defines
the message exchange pattern and operations that our contract supports.
My straightforward WSDL looks like this:
<?xml version="1.0" encoding="utf-8"?>
<wsdl:definitions name="HelloService"
targetNamespace="http://Seroter.BizTalk.SOA.Chapter5.ContractFirst"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://Seroter.BizTalk.SOA.Chapter5.ContractFirst"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<!-- declare types-->
<wsdl:types>
<xsd:schema elementFormDefault="qualified" xmlns="http://Seroter.BizTalk.SOA.Chapter5.ContractFirst" targetNamespace="http://Seroter.BizTalk.SOA.Chapter5.ContractFirst">
<xsd:element name="SiteRegistration">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="SiteID" type="xsd:string" />
<xsd:element name="SiteLocation" type="xsd:string" />
<xsd:element name="PrimaryPhysicianID" type="xsd:string" />
<xsd:element name="ActiveTrials">
<xsd:complexType>
<xsd:sequence>
<xsd:element minOccurs="0" maxOccurs="unbounded" name="TrialID" type="xsd:string" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
</wsdl:types>
<!-- declare messages-->
<wsdl:message name="SiteRegistrationRequest">
<wsdl:part name="part" element="tns:SiteRegistration" />
</wsdl:message>
<!-- decare port types-->
<wsdl:portType name="CustomSiteRegistration_PortType">
<wsdl:operation name="PublishSiteRegistration">
<wsdl:input message="tns:SiteRegistrationRequest" />
</wsdl:operation>
</wsdl:portType>
<!-- declare binding-->
<wsdl:binding name="tns:CustomSiteRegistration_Binding" type="tns:CustomSiteRegistration_PortType">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="PublishSiteRegistration">
<soap:operation soapAction="PublishSiteRegistration" style="document"/>
<wsdl:input>
<soap:body use ="literal"/>
</wsdl:input>
</wsdl:operation>
</wsdl:binding>
<!-- declare service-->
<wsdl:service name="CustomSiteRegistrationService">
<wsdl:port binding="CustomSiteRegistration_Binding" name="CustomSiteRegistration">
<soap:address location="http://localhost:4044/SiteRegistrationService"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>